1. First Analysis

Preparing data

gastroc <-
  readr::read_tsv("./data/counts/readcount_genename_gastroc.xls",
                  show_col_types = F) %>%
  tibble::column_to_rownames(var = "gene_id")
counts.gastroc <- gastroc[, 1:12]

# TODO: fix annotations: several entries are incomplete whereas the gastroc seems to be complete
soleus <- readr::read_tsv("./data/counts/readcount_genename_soleus.xls", show_col_types = F) %>%
  tibble::column_to_rownames(var = "gene_id")
Warning: One or more parsing issues, call `problems()` on your data frame for details, e.g.:
  dat <- vroom(...)
  problems(dat)
counts.soleus <- soleus[, 1:12]

Filtering

omitting genes with zero counts in more than 30% of the samples

most variable genes (top 1000) & Normalization

To obtain the most variable genes, a z-score will be applied per gene to then take the sd and filter for the top 1000

z-score:

\(\frac{x - mean(x)}{sd(x)}\)

zscore <- function(M) {
  s <- apply( M,1,sd )      # standard deviation
  µ <- apply( M, 1, mean )  # mean
  M.z <- (M - µ) / s        # zscore
  return(M.z)
}

# ' returning the raw counts of the most variable genes by applying zscore and sd
mostVariableRows <- function(M, ntop=1000) {
  M.z <- zscore(M)
  
  # ordering by sd descending
  sdev <- apply(M.z, 1, sd)
  M.top <- M[order(sdev, decreasing = T)[1:ntop] , ]
  return(M.top)
}

counts.gastroc.top <- mostVariableRows(counts.gastroc)
counts.soleus.top <- mostVariableRows(counts.soleus)

PCA

Here also taking the top 1000 most variable genes of each tissue and merging them.

This plot looks different than before! The overall cluster sill exist, but slightly shifted and rotated. The PC1 has now 48% whereas it previously had 40% and PC2 26% vs 25%.

I thought I did not change anything and after another plotting run, the plot changed. Cleaning environment and workspace did not change anything.

2. DESeq

Volcano Plot

volcanoPlot <- function(result, se, pCutoff = 0.05, FCutoff = 1, tissue = character()) {
  gene_names <-
    rowData(se)[rownames(result), c("gene_name"), drop = F]
  results.df <- result %>%
    as.data.frame() %>%
    dplyr::arrange(padj)
  
  # top 10 gene labels for respectively up and down regulation
  labs.up <- results.df[results.df$log2FoldChange > FCutoff, ] %>%
    rownames() %>% .[1:10] %>% gene_names[., c("gene_name")]
  labs.down <- results.df[results.df$log2FoldChange < -FCutoff, ] %>%
    rownames() %>% .[1:10] %>% gene_names[., c("gene_name")]
  selectLab <- c(labs.up, labs.down, "Nfe2l1") %>% unique() # always including "Nfe2l1"
  
  # custom colors:
  keyvals <- ifelse(
    result$log2FoldChange < -FCutoff &
      result$padj < pCutoff,
    'royalblue',
    ifelse(
      result$log2FoldChange > FCutoff &
        result$padj < pCutoff,
      'red',
      'gray'
    )
  )
  keyvals[is.na(keyvals)] <- 'gray'
  names(keyvals)[keyvals == 'red'] <- 'up regulated'
  names(keyvals)[keyvals == 'gray'] <- 'nonsignificant'
  names(keyvals)[keyvals == 'royalblue'] <- 'down regulated'
  
  vlc.plt <- EnhancedVolcano(
    result,
    x = 'log2FoldChange',
    y = 'padj',
    title = 'WT vs KO: Nfe2l1 knockout',
    subtitle = ifelse(isEmpty(tissue), "", paste0('tissue: ', tissue)),
    caption = "",
    ylab = expression(paste(-Log[10], p[adj])),
    pCutoff = pCutoff,
    FCcutoff = FCutoff,
    legendPosition = 'right',
    pointSize = 2,
    colCustom = keyvals,
    lab = gene_names$gene_name,
    selectLab = selectLab,
    labSize = 3,
    boxedLabels = TRUE,
    drawConnectors = TRUE,
    max.overlaps = Inf
  )
  
  return(vlc.plt)
}


p <- volcanoPlot(res.gastroc, se.gastroc, pCutoff = pCutoff, FCutoff = FCutoff, tissue = "gastrocnemius")
Error in rowData(se) : could not find function "rowData"

scatter-plot: most differential genes, both tissues

using the Wald-test stat from the DESeq2 result and filtering on the set FCutoff=1 and pCutoff=0.01 yields the following plot:

apply_cutoffs <- function(deseq.result, colname="stat", FCutoff, pCutoff) {
  res.filtered <- deseq.result %>%
    data.frame() %>%
    filter(padj < pCutoff,
           log2FoldChange > FCutoff | log2FoldChange < -FCutoff) %>%
    dplyr::rename(!!colname := stat) %>%
    dplyr::select(!!colname)
  return(res.filtered)
}

gastroc_res.filtered <- apply_cutoffs(res.gastroc, colname="gastroc", FCutoff, pCutoff)
soleus_res.filtered  <- apply_cutoffs(res.soleus,  colname="soleus",  FCutoff, pCutoff)

gene_names <- rowData(se.gastroc) %>% as.data.frame() %>% 
  dplyr::select(gene_name)

# combining Wald-Test data from both tissues and ordering in quadrants
res.combined <- merge(gastroc_res.filtered,
                      soleus_res.filtered,
                      by = 0) %>%
  mutate(diff.exp = case_when(
    gastroc < 0 & soleus < 0 ~ "both down",
    gastroc > 0 & soleus > 0 ~ "both up",
    gastroc < 0 & soleus > 0 ~ "gastrocnemius down,\n soleus up",
    gastroc > 0 & soleus < 0 ~ "gastrocnemus up,\n soleus down",
    TRUE ~ "different"
  )) %>% 
  merge(gene_names, by.x="Row.names", by.y=0)
 
# removing all gene_names except the top_n_genes (sum of absolute Wald-test numbers)
top_n_genes <- 10
top_labels <- res.combined %>%
  group_by(diff.exp) %>% 
  arrange(desc(abs(gastroc) + abs(soleus))) %>%
  filter(row_number() %in% c(1:top_n_genes)) %>% 
  ungroup() %>% 
  .$gene_name

res.combined <- res.combined %>% 
  mutate(gene_name = ifelse(gene_name %in% top_labels, gene_name, ""))

# final plot
p <- ggplot(res.combined, aes(x = gastroc, y = soleus, label = gene_name)) +
  geom_vline(xintercept = 0) + 
  geom_hline(yintercept = 0) + 
  geom_point(aes(color = diff.exp)) +
  # scale_color_manual(values = c("red", "chartreuse1", "bisque", "royalblue")) +
  labs(x = "gastroc", y = "soleus", color = "significantly\ndifferentially\nexpressed") +
  ggrepel::geom_label_repel(max.overlaps = 15) + 
  ggtitle(label = "DEA: t-statistics (Wald test)") +
  theme_bw() +
  theme(
    axis.title = element_text(size = 13),
    axis.text = element_text(size = 13),
    title = element_text(size = 13),
    legend.text = element_text(size = 10)
  )

ggsave(filename = file.path(path.currentPlots, "02_dea_scatter.svg"), p)
Saving 7 x 7 in image
p

barplot

Venn/Euler-Diagram

Heatmap

library(ComplexHeatmap)
library(RColorBrewer)

zscore <- function(M) {
  s <- apply( M,1,sd )      # standard deviation
  µ <- apply( M, 1, mean )  # mean
  M.z <- (M - µ) / s        # zscore
  return(M.z)
}

sign_genes <-
  unique(c(
    row.names(gastroc_res.filtered),
    row.names(soleus_res.filtered)
  )) 
sign_genes <-
  sign_genes[sign_genes %in% rownames(counts.gastroc) &
               sign_genes %in% rownames(counts.soleus)]

counts_sign_zscored <- merge(
  counts.gastroc[sign_genes,],
  counts.soleus[sign_genes,],
  by = 0,
  suffixes = c(".gastroc", ".soleus")
) %>% 
  tibble::column_to_rownames(var="Row.names") %>%
  as.matrix() %>% 
  zscore()


plotCHM <- function(counts_sign) {
  mypalette <- brewer.pal(11, "RdYlBu")
  # (reverse to map style of other heatmap in manuscript)
  morecols <- colorRampPalette(rev(mypalette))
  
  tissue_vec <- c(rep("gastroc", 12), rep("soleus", 12))
  condition_vec <- c(rep(c(rep("WT", 6), rep("KO", 6)),2))
  top_annot <-
    HeatmapAnnotation(
      tissue = tissue_vec,
      condition = condition_vec,
      col = list(tissue = tissue_pal, condition = c("WT" = "white", "KO" = "gray")),
      gp = gpar(col = "darkgray"),
      show_legend = FALSE
    )
  
  chm <- Heatmap(
    counts_sign,
    row_title = "differentially expressed genes",
    name = "zscore",
    show_row_names = FALSE,
    show_column_names = FALSE,
    column_title = "samples",
    col = rev(morecols(50)),
    column_title_side = "bottom",
    top_annotation = top_annot
  )
  
  # creating custom annotation legend (to obtain the gray border)
  condition_legend = Legend(
    labels = c("WT", "KO"),
    legend_gp = gpar(fill = c("white", "gray")),
    border = "darkgray",
    title = "condition"
  )
  tissue_legend = Legend(
    labels = c("gastroc", "soleus"),
    legend_gp = gpar(fill = unname(tissue_pal)),
    border = "darkgray",
    title = "tissue"
  )
  legend_list <- list(condition_legend, tissue_legend)
  
  draw(chm, annotation_legend_list = legend_list)
}

svglite::svglite(
  file.path(path.currentPlots, "03_heatmap_sign_genes.svg"),
  width = 6,
  height = 6
)
plotCHM(counts_sign_zscored)
dev.off()
null device 
          1 

pca: significant genes

Here the z-scored count matrix is used to create the pca plot. Wether the counts are z-scored or not, does not change the plot, as long as the z-score is applied on the whole matrix (not per tissue)

pca <- prcomp(t(counts_sign_zscored), scale. = T)

pca.data <- data.frame(Sample = rownames(pca$x),
                     X = pca$x[, 1],
                     Y = pca$x[, 2]) %>%
mutate(condition = substr(Sample, 1, 2),
       tissue = stringr::str_extract(Sample,"[:alpha:]+$"))

p <-
autoplot(
  pca,
  data = pca.data,
  colour = 'tissue',
  shape = 'condition',
  label.show.legend = FALSE
) +
  ggtitle("PCA z-scored sign. genes") +
  scale_color_manual(values = unname(tissue_pal)) +
  theme_bw()
ggsave(filename = file.path(path.currentPlots, "03_pca_dea_sign_zscored.svg"), p)
p

3. fgsea

ranks

getRanks <- function(res, annot) {
  # only taking genes which have entrezgene_ids assigned to them
  genes_with_entrez <- select(annot, GeneID, entrezgene_id) %>% 
    filter(!is.na(entrezgene_id))
  
  ranks <- as.data.frame(res) %>%
    tibble::rownames_to_column("GeneID") %>%
    merge(genes_with_entrez, by = "GeneID") %>%
    arrange(desc(stat)) %>% 
    select(entrezgene_id, stat) %>% 
    tibble::deframe() # creating a named num from two columns
  return(ranks)
}

ranks.gastroc <- getRanks(res.gastroc, annot)
ranks.soleus <- getRanks(res.soleus, annot)
# TODO: why (again) is the soleus gene count seemingly 300 below soleus gene count / ranks count
# -> seems because of e.g. the cutoff at DESeq

applying fgsea

fgseaRes <- fgsea(
  pathways = CGP,
  stats    = ranks,
  minSize  = 15,
  maxSize  = 200
)

most differential regulated pathways, both tissues

using NES from the fgsea result filtering on the set `pCutoff=`0.01 yields the following plot:


pCutoff <- params$pCutoff
fgseaRes.combined <- merge(
  data.frame(fgseaRes.gastroc[, c("pathway", "NES", "padj")]),
  data.frame(fgseaRes.soleus[, c("pathway", "NES", "padj")]),
  by = "pathway",
  suffixes = c(".ga", ".sol")
) %>%
  filter(padj.ga < pCutoff | padj.sol < pCutoff) %>%
  mutate(
    diff.exp = case_when(
      NES.ga  < 0 & NES.sol < 0 & padj.ga < pCutoff & padj.sol < pCutoff ~ "both down",
      NES.ga  > 0 & NES.sol > 0 & padj.ga < pCutoff & padj.sol < pCutoff ~ "both up",
      NES.ga  < 0 & NES.sol > 0 & padj.ga < pCutoff & padj.sol < pCutoff ~ "gastrocnemius down, soleus up",
      NES.ga  > 0 & NES.sol < 0 & padj.ga < pCutoff & padj.sol < pCutoff ~ "gastrocnemius up, soleus down",
                                  padj.ga < pCutoff & padj.sol > pCutoff ~ "only gastrocnemius",
      # NES.ga  > 0 &               padj.ga < pCutoff & padj.sol > pCutoff ~ "ga up",
                                  padj.ga > pCutoff & padj.sol < pCutoff ~ "only soleus",
      # NES.sol > 0 &               padj.ga > pCutoff & padj.sol < pCutoff ~ "sol up",
      TRUE ~ "different"
    )
  )

# final plot
p <- ggplot(fgseaRes.combined, aes(x = NES.ga, y = NES.sol, text=pathway)) +
  geom_vline(xintercept = 0) + 
  geom_hline(yintercept = 0) + 
  geom_point(aes(color = diff.exp)) +
  # scale_color_manual(values = c("red", "chartreuse1", "bisque", "royalblue")) +
  labs(x = "gastrocnemius", y = "soleus", color = "significant\ndifferentially\nexpressed") +
  # ggrepel::geom_label_repel(max.overlaps = 20) + 
  ggtitle(label = "NES") +
  theme_bw()

ggsave(filename = file.path(path.currentPlots, "03_gsea_scatter.svg"), p)
Saving 7 x 7 in image
p


# interactive:
# plotly::ggplotly(p, tooltip = "all")

barplot

p <- ggplot(fgseaRes.combined, aes(x = diff.exp)) +
  geom_bar(aes(fill = diff.exp)) +
  theme_bw()

ggsave(filename = file.path(path.currentPlots, "03_gsea_scatter_barplot.svg"), p)
Saving 7 x 7 in image
p

Venn/Euler-Diagram

col_palette <- RColorBrewer::brewer.pal(8, "Dark2")
# venn.colors <- c(gastroc = "#FFB08E", soleus = "#FF6638", col_palette[1:4])
venn.colors <- c(tissue_pal, scales::hue_pal()(4))

sign_geneSets_gastroc <-
  data.frame(fgseaRes.gastroc[, c("pathway", "padj")]) %>%
  filter(padj < pCutoff) %>% 
  pull(pathway)


sign_geneSets_soleus <-
  data.frame(fgseaRes.soleus[, c("pathway", "padj")]) %>%
  filter(padj < pCutoff) %>% 
  pull(pathway)

# only the two tissues
gene_sets <- list(
  "gastroc" = sign_geneSets_gastroc,
  "soleus" = sign_geneSets_soleus#,
  # "gastroc&soleus" = sign_gene_stats$shared_sig_genes
)

# 1st option: euler two tissues
p <- plot(
  euler(gene_sets),
  quantities = T,
  legend = list(side = "right"),
  fills = venn.colors,
  main = "significant gene sets (GSEA)"
)
ggsave(filename = file.path(path.currentPlots, "03_euler.svg"), p)
Saving 7 x 7 in image
p


# 2nd option: venn two tissues
library(eulerr)
p <- plot(
  eulerr::venn(gene_sets),
  fills = tissue_pal,
  main = "significant gene sets (GSEA)"
)
ggsave(filename = file.path(path.currentPlots, "03_venn.svg"), p)
Saving 6.9 x 4.26 in image
p

dotplot

combined_dotplot <-
  function(group,
           fgseaRes.combined = fgseaRes.combined,
           max_label_length = 25, topn=6e6, sort_by_gastroc=T) {
    
    # get pathways of the respective group
    pathways <- filter(fgseaRes.combined, diff.exp == group)$pathway
    
    gastroc.df <-
      filter(fgseaRes.gastroc, pathway %in% pathways)
    soleus.df <-
      filter(fgseaRes.soleus, pathway %in% gastroc.df$pathway)
    
    if (sort_by_gastroc) {
      gastroc.df <- gastroc.df %>% 
        top_n(topn, wt = abs(NES)) %>%
        arrange(NES)
      soleus.df <- soleus.df %>% 
        .[match(gastroc.df$pathway, pathway)]
    } else {
      soleus.df <- soleus.df %>% 
        top_n(topn, wt = abs(NES)) %>%
        arrange(NES)
      gastroc.df <- gastroc.df %>% 
        .[match(soleus.df$pathway, pathway)]
    }
    
    padj_limits <-
      c(min(gastroc.df$padj, soleus.df$padj),
        max(gastroc.df$padj, soleus.df$padj))
    
    NES_limits <- 
      c(min(gastroc.df$NES, soleus.df$NES),
        max(gastroc.df$NES, soleus.df$NES))
    
    p.gastroc <- fgsea_dotplot(gastroc.df, max_label_length, padj_limits, NES_limits)
    
    p.soleus <- fgsea_dotplot(soleus.df, max_label_length, padj_limits, NES_limits)
    
    # adjust plots
    p.gastroc <- p.gastroc +
      theme(
        legend.position = "none",
        plot.margin = unit(c(0, 0, 0, 0), "npc"),
      ) +
      ylab("NES\n(gastroc)") + 
      xlab("")
    p.soleus <- p.soleus +
      theme(
        axis.text.y = element_blank(),
        plot.margin = unit(c(0, 0, 0, 0), "npc")
      ) +
      ylab("NES\n(soleus)") + 
      xlab("")
    
    return(p.gastroc + p.soleus)
  }


fgsea_dotplot <-
  function(fgseaRes.df,
           max_label_length=25,
           padj_limits=NULL,
           NES_limits=NULL) {
  size_breaks <- seq(0,200,by=50)
  size_breaks[1] <- 10
  size_range <- c(2,8)
  
  # adjust label size if the plot has too many sets:
  label_size <- 8
  nsets <- nrow(fgseaRes.df)
  if (nsets > 15) {
    
    label_size <- label_size - (0.05 * nsets)
    max_label_length <- nsets + 10
    size_range <- size_range -1
  }
  
  fgseaRes_prep.df <- fgseaRes.df %>%
    # TODO: might need to be changed if other labels are used (like description)
    mutate(pathway = gsub("_", " ", pathway) %>% tolower()) %>%
    mutate(pathway = factor(pathway, levels = pathway))
  # the factor levels might be different than the order of the rows
  
  ggplot(fgseaRes_prep.df, aes(x = pathway, y = NES)) +
    geom_point(aes(size = size, color = padj)) +
    scale_color_gradient(low = "red",
                         high = "blue",
                         limits = padj_limits) +
    scale_size_continuous(range = size_range,
                          breaks = size_breaks,
                          limits = c(10, 200)) +
    scale_y_continuous(limits = NES_limits) +
    # scale_x_discrete(guide = guide_axis(check.overlap = TRUE))+
    scale_x_discrete(labels = scales::label_wrap(max_label_length)) +
    xlab("Gene Set") + ylab("NES") +
    labs(size = "Count", color = "p.adjust") +
    theme_bw() +
    coord_flip() +
    theme(axis.text.y = element_text(size = label_size))
  }

!Note: Different to most other plots, the dotplots seem to be created best by running the chunks manually. This results in automatic axis scaling with more entries, so that the labels do not overlap (which is curiously not the case if remotely knitting)!

dotplot: “both upregulated”

only 7 gene sets => fits well in the plot

group = "both up"
p_combined <- combined_dotplot(group, fgseaRes.combined)

ggsave(filename = file.path(path.currentPlots, paste0("03_gsea_dotplot_", group, ".svg")),
       p_combined)
Saving 7 x 7 in image
p_combined

dotplot: “both down”

gets adjusted by the function well. The .svg file looks okay

group = "both down"
p_combined <- combined_dotplot(group, fgseaRes.combined)

ggsave(filename = file.path(path.currentPlots, paste0("03_gsea_dotplot_", group, ".svg")),
       p_combined)
Saving 7 x 7 in image
p_combined

dotplot: “only gastrocnemius”

gets adjusted by the function well. The .svg file looks okay

group = "only gastrocnemius"
p_combined <- combined_dotplot(group, fgseaRes.combined, max_label_length = 100)

ggsave(filename = file.path(path.currentPlots, paste0("03_gsea_dotplot_", group, ".svg")),
       p_combined)
Saving 7 x 7 in image
p_combined

group = "only gastrocnemius"
pathways <- filter(fgseaRes.combined, diff.exp == group)$pathway

gastroc.df <-
  filter(fgseaRes.gastroc, pathway %in% pathways) %>%
  arrange(NES)

p.gastroc <-
  fgsea_dotplot(gastroc.df, max_label_length = 100)
# ggsave(filename = file.path(path.currentPlots, paste0("03_gsea_dotplot_", group, "_single.svg")),
#        p.gastroc)
p.gastroc

dotplot: “only soleus”

gets adjusted by the function well. The .svg file looks okay

group = "only soleus"
p_combined <- combined_dotplot(group, fgseaRes.combined, max_label_length = 100, sort_by_gastroc = F)

ggsave(filename = file.path(path.currentPlots, paste0("03_gsea_dotplot_", group, ".svg")),
       p_combined)
Saving 7 x 7 in image
p_combined

group = "only soleus"
pathways <- filter(fgseaRes.combined, diff.exp == group)$pathway

soleus.df <-
  filter(fgseaRes.soleus, pathway %in% pathways) %>%
  arrange(NES)

p.soleus <-
  fgsea_dotplot(soleus.df, max_label_length = 100)
# ggsave(filename = file.path(path.currentPlots, paste0("03_gsea_dotplot_", group, "_single.svg")),
#        p.soleus)
p.soleus

dotplot: top20

LS0tCnRpdGxlOiAiMDdfY3VycmVudFBsb3RzIgphdXRob3I6ICJOaWNrIERpZXJja3NlbiIKZGF0ZTogIkxhc3QgY29tcGlsZWQgb24gYHIgZm9ybWF0KFN5cy50aW1lKCkpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCnBhcmFtczoKICBGQ3V0b2ZmOgogICAgbGFiZWw6IGN1dG9mZiBmb3IgdGhlIGxvZzJGb2xkQ2hhbmdlCiAgICB2YWx1ZTogMQogIHBDdXRvZmY6CiAgICBsYWJlbDogImN1dG9mZiBmb3IgdGhlIGFkanVzdGVkIHAtVmFsdWUiCiAgICB2YWx1ZTogMC4wMQogIGtuaXR0aW5nX3BhcmFtZXRlcnNfdXNlZDoKICAgIGxhYmVsOiAiaGVscGVyIHZhcmlhYmxlIGZvciBrbml0dGluZywgc2hvdWxkIG5vdCBiZSBjaGFuZ2VkIgogICAgdmFsdWU6IFRSVUUKICBmZ3NlYS5tYXhTaXplOgogICAgbGFiZWw6ICJwYXJhbWV0ZXIgZm9yIGBmZ3NlYWA6ICdNYXhpbWFsIHNpemUgb2YgYSBnZW5lIHNldCB0byB0ZXN0LiBBbGwgcGF0aHdheXMgYWJvdmUgdGhlIHRocmVzaG9sZCBhcmUgZXhjbHVkZWQuJyIKICAgIHZhbHVlOiAyMDAKICByZWV2YWx1YXRlOgogICAgbGFiZWw6IFNob3VsZCBhbGwgY2FsY3VsYXRpb25zIGFuZCBkYXRhYmFzZSBsb29rdXBzIGJlIGRvbmUgYWdhaW4gb3IganVzdCBlLmcuCiAgICAgIGdyYXBoaWNzIHJlZ2VuZXJhdGVkCiAgICB2YWx1ZTogbm8KLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dmb3J0aWZ5KSAjIG5lZWQgZm9yIGBhdXRvcGxvdCgpYCAoUENBKQoKIyBsaWJyYXJ5KGNpcmNsaXplKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKIyBsaWJyYXJ5KFN1bW1hcml6ZWRFeHBlcmltZW50KQpgYGAKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CnBhdGguY3VycmVudFBsb3RzIDwtIGZpbGUucGF0aCgiLiIsICJwbG90cyIsICJjdXJyZW50IikKYGBgCgoKCmBgYHtyIHBsb3RTZXR0aW5ncywgaW5jbHVkZT1GQUxTRX0KY29uZGl0aW9uX3BhbCA8LSBjKCJXVCI9ICIjMDBDM0M2IiwiS08iPSAiI0ZGNkM2NyIpCgojIGNvbG9yIHBhbGV0dGUgZm9yIHRoZSB0aXNzdWVzCiMgdGlzc3VlX3BhbCA8LSBjKGdhc3Ryb2MgPSAiI0ZGQjA4RSIsIHNvbGV1cz0iI0ZGNjYzOCIpCnRpc3N1ZV9wYWwgPC0gYyhnYXN0cm9jID0gIm9yYW5nZSIsIHNvbGV1cz0icHVycGxlIikKYGBgCgojIDEuIEZpcnN0IEFuYWx5c2lzCgojIyBQcmVwYXJpbmcgZGF0YQoKYGBge3J9Cmdhc3Ryb2MgPC0KICByZWFkcjo6cmVhZF90c3YoIi4vZGF0YS9jb3VudHMvcmVhZGNvdW50X2dlbmVuYW1lX2dhc3Ryb2MueGxzIiwKICAgICAgICAgICAgICAgICAgc2hvd19jb2xfdHlwZXMgPSBGKSAlPiUKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiZ2VuZV9pZCIpCmNvdW50cy5nYXN0cm9jIDwtIGdhc3Ryb2NbLCAxOjEyXQoKIyBUT0RPOiBmaXggYW5ub3RhdGlvbnM6IHNldmVyYWwgZW50cmllcyBhcmUgaW5jb21wbGV0ZSB3aGVyZWFzIHRoZSBnYXN0cm9jIHNlZW1zIHRvIGJlIGNvbXBsZXRlCnNvbGV1cyA8LSByZWFkcjo6cmVhZF90c3YoIi4vZGF0YS9jb3VudHMvcmVhZGNvdW50X2dlbmVuYW1lX3NvbGV1cy54bHMiLCBzaG93X2NvbF90eXBlcyA9IEYpICU+JQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKHZhciA9ICJnZW5lX2lkIikKY291bnRzLnNvbGV1cyA8LSBzb2xldXNbLCAxOjEyXQoKYGBgCgojIyMgRmlsdGVyaW5nCgpvbWl0dGluZyBnZW5lcyB3aXRoIHplcm8gY291bnRzIGluIG1vcmUgdGhhbiAzMCUgb2YgdGhlIHNhbXBsZXMKCmBgYHtyIG9taXRfemVyb19jb3VudHMsIGVjaG89RkFMU0V9Cm9taXRaZXJvQ291bnRzIDwtIGZ1bmN0aW9uKGNvdW50cy5yYXcsIHNoYXJlPTAuMykgewogIGtlZXAgPC0gKGNvdW50cy5yYXcgIT0gMCkgJT4lCiAgICByb3dTdW1zKCkgPiByb3VuZChzaGFyZSAqIG5jb2woY291bnRzLnJhdykpCiAgcmV0dXJuKGNvdW50cy5yYXdba2VlcCxdKQp9Cgpjb3VudHMuZ2FzdHJvYyA8LSBvbWl0WmVyb0NvdW50cyhjb3VudHMuZ2FzdHJvYykKY291bnRzLnNvbGV1cyA8LSBvbWl0WmVyb0NvdW50cyhjb3VudHMuc29sZXVzKQpgYGAKCgojIyBtb3N0IHZhcmlhYmxlIGdlbmVzICh0b3AgMTAwMCkgJiBOb3JtYWxpemF0aW9uClRvIG9idGFpbiB0aGUgbW9zdCB2YXJpYWJsZSBnZW5lcywgYSBgei1zY29yZWAgd2lsbCBiZSBhcHBsaWVkIHBlciBnZW5lIHRvIHRoZW4KdGFrZSB0aGUgYHNkYCBhbmQgZmlsdGVyIGZvciB0aGUgdG9wIDEwMDAKCgp6LXNjb3JlOgoKJFxmcmFje3ggLSBtZWFuKHgpfXtzZCh4KX0kCgpgYGB7ciB6c2NvcmV9CnpzY29yZSA8LSBmdW5jdGlvbihNKSB7CiAgcyA8LSBhcHBseSggTSwxLHNkICkgICAgICAjIHN0YW5kYXJkIGRldmlhdGlvbgogIMK1IDwtIGFwcGx5KCBNLCAxLCBtZWFuICkgICMgbWVhbgogIE0ueiA8LSAoTSAtIMK1KSAvIHMgICAgICAgICMgenNjb3JlCiAgcmV0dXJuKE0ueikKfQoKIyAnIHJldHVybmluZyB0aGUgcmF3IGNvdW50cyBvZiB0aGUgbW9zdCB2YXJpYWJsZSBnZW5lcyBieSBhcHBseWluZyB6c2NvcmUgYW5kIHNkCm1vc3RWYXJpYWJsZVJvd3MgPC0gZnVuY3Rpb24oTSwgbnRvcD0xMDAwKSB7CiAgTS56IDwtIHpzY29yZShNKQogIAogICMgb3JkZXJpbmcgYnkgc2QgZGVzY2VuZGluZwogIHNkZXYgPC0gYXBwbHkoTS56LCAxLCBzZCkKICBNLnRvcCA8LSBNW29yZGVyKHNkZXYsIGRlY3JlYXNpbmcgPSBUKVsxOm50b3BdICwgXQogIHJldHVybihNLnRvcCkKfQoKY291bnRzLmdhc3Ryb2MudG9wIDwtIG1vc3RWYXJpYWJsZVJvd3MoY291bnRzLmdhc3Ryb2MpCmNvdW50cy5zb2xldXMudG9wIDwtIG1vc3RWYXJpYWJsZVJvd3MoY291bnRzLnNvbGV1cykKYGBgCgoKIyMgUENBCgpIZXJlIGFsc28gdGFraW5nIHRoZSB0b3AgMTAwMCBtb3N0IHZhcmlhYmxlIGdlbmVzIG9mIGVhY2ggdGlzc3VlIGFuZCBtZXJnaW5nIHRoZW0uCgpgYGB7ciBwY2FfZmlsdGVyZF9nZW5lcywgbWVzc2FnZT1GQUxTRX0KCm1vc3RfdmFyaWFibGVfZ2VuZXMgPC0gYyhyb3cubmFtZXMoY291bnRzLmdhc3Ryb2MudG9wKSwgcm93Lm5hbWVzKGNvdW50cy5zb2xldXMudG9wKSkgJT4lIAogIHVuaXF1ZSgpCgpjb3VudHMuYm90aCA8LQogIG1lcmdlKGNvdW50cy5nYXN0cm9jW21vc3RfdmFyaWFibGVfZ2VuZXMsIF0sCiAgICAgICAgY291bnRzLnNvbGV1c1ttb3N0X3ZhcmlhYmxlX2dlbmVzLCBdLAogICAgICAgIGJ5ID0gMCwKICAgICAgICBhbGwgPSBULAogICAgICAgIHN1ZmZpeGVzID0gYygiX2dhc3Ryb2MiLCAiX3NvbGV1cyIpKSAlPiUKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcygiUm93Lm5hbWVzIikgJT4lIAogIG5hLm9taXQoKQoKcGNhIDwtIHByY29tcCh0KGNvdW50cy5ib3RoKSwgc2NhbGUuID0gVCkKCnBjYS5kYXRhIDwtIGRhdGEuZnJhbWUoU2FtcGxlID0gcm93bmFtZXMocGNhJHgpLAogICAgICAgICAgICAgICAgICAgICAgIFggPSBwY2EkeFssIDFdLAogICAgICAgICAgICAgICAgICAgICAgIFkgPSBwY2EkeFssIDJdKSAlPiUKICBtdXRhdGUoY29uZGl0aW9uID0gc3Vic3RyKFNhbXBsZSwgMSwgMiksCiAgICAgICAgIHRpc3N1ZSA9IHN0cmluZ3I6OnN0cl9leHRyYWN0KFNhbXBsZSwiWzphbHBoYTpdKyQiKSkKCnBsdCA8LQogIGF1dG9wbG90KAogICAgcGNhLAogICAgZGF0YSA9IHBjYS5kYXRhLAogICAgY29sb3VyID0gJ3Rpc3N1ZScsCiAgICBzaGFwZSA9ICdjb25kaXRpb24nLAogICAgbGFiZWwuc2hvdy5sZWdlbmQgPSBGQUxTRQogICkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSB0aXNzdWVfcGFsKSArCiAgZ2d0aXRsZSgiUENBIHRvcCAxMDAwIG1vc3QgdmFyaWFibGUgZ2VuZXMgb2YgZWFjaCB0aXNzdWUgKHNkKSIpKwogIHRoZW1lX2J3KCkKZ2dzYXZlKGZpbGVuYW1lID0gZmlsZS5wYXRoKHBhdGguY3VycmVudFBsb3RzLCAiMDFfcGNhX2JvdGhfdGlzc3Vlc19maWx0ZXJlZC5zdmciKSwgcGx0KQpwbHQKYGBgCgpUaGlzIHBsb3QgbG9va3MgZGlmZmVyZW50IHRoYW4gYmVmb3JlISBUaGUgb3ZlcmFsbCBjbHVzdGVyIHNpbGwgZXhpc3QsIGJ1dCBzbGlnaHRseSBzaGlmdGVkIGFuZCByb3RhdGVkLiBUaGUgUEMxIGhhcyBub3cgNDglIHdoZXJlYXMgaXQgcHJldmlvdXNseSBoYWQgNDAlIGFuZCBQQzIgMjYlIHZzIDI1JS4KCkkgdGhvdWdodCBJIGRpZCBub3QgY2hhbmdlIGFueXRoaW5nIGFuZCBhZnRlciBhbm90aGVyIHBsb3R0aW5nIHJ1biwgdGhlIHBsb3QgY2hhbmdlZC4gQ2xlYW5pbmcgZW52aXJvbm1lbnQgYW5kIHdvcmtzcGFjZSBkaWQgbm90IGNoYW5nZSBhbnl0aGluZy4KCgojIDIuIERFU2VxCgpgYGB7ciBzZXR1cERlc2VxLCBpbmNsdWRlPUZBTFNFfQppZiAoISAiQ29tcGxleEhlYXRtYXAiICVpbiUgcm93Lm5hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkgewogIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1Yigiam9rZXJnb28vQ29tcGxleEhlYXRtYXAiLCBmb3JjZSA9IFRSVUUpCiAgIyBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiQ29tcGxleEhlYXRtYXAiKQp9CmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkoRW5oYW5jZWRWb2xjYW5vKQpsaWJyYXJ5KGV1bGVycikKbGlicmFyeShTdW1tYXJpemVkRXhwZXJpbWVudCkKCmxvYWQoIi4vZGF0YS9Sb2JqZWN0cy8wM19ERFMuUkRhdGEiKQoKc2UuZ2FzdHJvYyA8LSByZWFkUkRTKCIuL2RhdGEvUm9iamVjdHMvMDFfc2UuZ2FzdHJvYy5yZHMiKQpzZS5zb2xldXMgPC0gcmVhZFJEUygiLi9kYXRhL1JvYmplY3RzLzAxX3NlLnNvbGV1cy5yZHMiKQoKaWYgKCEgZXhpc3RzKCJrbml0dGluZ19wYXJhbWV0ZXJzX3VzZWQiKSkgewogICMgd2hlbiBrbml0dGluZyB3aXRoIHBhcmFtZXRlcnMgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgYXJlIGFscmVhZHkgc2V0CiAgRkN1dG9mZiA8LSAxCiAgcEN1dG9mZiA8LSAwLjAxCn0gZWxzZSB7CiAgRkN1dG9mZiA8LSBwYXJhbXMkRkN1dG9mZgogIHBDdXRvZmYgPC0gcGFyYW1zJHBDdXRvZmYKfSAKYGBgCgoKIyMgVm9sY2FubyBQbG90CmBgYHtyIHZvbGNhbm9QbG90LCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gOSwgZmlnLnJldGluYSA9IDIsIGRwaSA9IDEwMH0Kdm9sY2Fub1Bsb3QgPC0gZnVuY3Rpb24ocmVzdWx0LCBzZSwgcEN1dG9mZiA9IDAuMDEsIEZDdXRvZmYgPSAxLCB0aXNzdWUgPSBjaGFyYWN0ZXIoKSkgewogIGdlbmVfbmFtZXMgPC0KICAgIHJvd0RhdGEoc2UpW3Jvd25hbWVzKHJlc3VsdCksIGMoImdlbmVfbmFtZSIpLCBkcm9wID0gRl0KICByZXN1bHRzLmRmIDwtIHJlc3VsdCAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIGRwbHlyOjphcnJhbmdlKHBhZGopCiAgCiAgIyB0b3AgMTAgZ2VuZSBsYWJlbHMgZm9yIHJlc3BlY3RpdmVseSB1cCBhbmQgZG93biByZWd1bGF0aW9uCiAgbGFicy51cCA8LSByZXN1bHRzLmRmW3Jlc3VsdHMuZGYkbG9nMkZvbGRDaGFuZ2UgPiBGQ3V0b2ZmLCBdICU+JQogICAgcm93bmFtZXMoKSAlPiUgLlsxOjEwXSAlPiUgZ2VuZV9uYW1lc1suLCBjKCJnZW5lX25hbWUiKV0KICBsYWJzLmRvd24gPC0gcmVzdWx0cy5kZltyZXN1bHRzLmRmJGxvZzJGb2xkQ2hhbmdlIDwgLUZDdXRvZmYsIF0gJT4lCiAgICByb3duYW1lcygpICU+JSAuWzE6MTBdICU+JSBnZW5lX25hbWVzWy4sIGMoImdlbmVfbmFtZSIpXQogIHNlbGVjdExhYiA8LSBjKGxhYnMudXAsIGxhYnMuZG93biwgIk5mZTJsMSIpICU+JSB1bmlxdWUoKSAjIGFsd2F5cyBpbmNsdWRpbmcgIk5mZTJsMSIKICAKICAjIGN1c3RvbSBjb2xvcnM6CiAga2V5dmFscyA8LSBpZmVsc2UoCiAgICByZXN1bHQkbG9nMkZvbGRDaGFuZ2UgPCAtRkN1dG9mZiAmCiAgICAgIHJlc3VsdCRwYWRqIDwgcEN1dG9mZiwKICAgICdyb3lhbGJsdWUnLAogICAgaWZlbHNlKAogICAgICByZXN1bHQkbG9nMkZvbGRDaGFuZ2UgPiBGQ3V0b2ZmICYKICAgICAgICByZXN1bHQkcGFkaiA8IHBDdXRvZmYsCiAgICAgICdyZWQnLAogICAgICAnZ3JheScKICAgICkKICApCiAga2V5dmFsc1tpcy5uYShrZXl2YWxzKV0gPC0gJ2dyYXknCiAgbmFtZXMoa2V5dmFscylba2V5dmFscyA9PSAncmVkJ10gPC0gJ3VwIHJlZ3VsYXRlZCcKICBuYW1lcyhrZXl2YWxzKVtrZXl2YWxzID09ICdncmF5J10gPC0gJ25vbnNpZ25pZmljYW50JwogIG5hbWVzKGtleXZhbHMpW2tleXZhbHMgPT0gJ3JveWFsYmx1ZSddIDwtICdkb3duIHJlZ3VsYXRlZCcKICAKICB2bGMucGx0IDwtIEVuaGFuY2VkVm9sY2FubygKICAgIHJlc3VsdCwKICAgIHggPSAnbG9nMkZvbGRDaGFuZ2UnLAogICAgeSA9ICdwYWRqJywKICAgIHRpdGxlID0gJ1dUIHZzIEtPOiBOZmUybDEga25vY2tvdXQnLAogICAgc3VidGl0bGUgPSBpZmVsc2UoaXNFbXB0eSh0aXNzdWUpLCAiIiwgcGFzdGUwKCd0aXNzdWU6ICcsIHRpc3N1ZSkpLAogICAgY2FwdGlvbiA9ICIiLAogICAgeWxhYiA9IGV4cHJlc3Npb24ocGFzdGUoLUxvZ1sxMF0sIHBbYWRqXSkpLAogICAgcEN1dG9mZiA9IHBDdXRvZmYsCiAgICBGQ2N1dG9mZiA9IEZDdXRvZmYsCiAgICBsZWdlbmRQb3NpdGlvbiA9ICdyaWdodCcsCiAgICBwb2ludFNpemUgPSAyLAogICAgY29sQ3VzdG9tID0ga2V5dmFscywKICAgIGxhYiA9IGdlbmVfbmFtZXMkZ2VuZV9uYW1lLAogICAgc2VsZWN0TGFiID0gc2VsZWN0TGFiLAogICAgbGFiU2l6ZSA9IDMsCiAgICBib3hlZExhYmVscyA9IFRSVUUsCiAgICBkcmF3Q29ubmVjdG9ycyA9IFRSVUUsCiAgICBtYXgub3ZlcmxhcHMgPSBJbmYKICApCiAgCiAgcmV0dXJuKHZsYy5wbHQpCn0KCgpwIDwtIHZvbGNhbm9QbG90KHJlcy5nYXN0cm9jLCBzZS5nYXN0cm9jLCBwQ3V0b2ZmID0gcEN1dG9mZiwgRkN1dG9mZiA9IEZDdXRvZmYsIHRpc3N1ZSA9ICJnYXN0cm9jbmVtaXVzIikKZ2dzYXZlKGZpbGVuYW1lID0gZmlsZS5wYXRoKHBhdGguY3VycmVudFBsb3RzLCAiMDJfdm9sY2Fub19nYXN0cm9jLnN2ZyIpLCBwKQpwCnAgPC0gdm9sY2Fub1Bsb3QocmVzLnNvbGV1cywgc2Uuc29sZXVzLCBwQ3V0b2ZmID0gcEN1dG9mZiwgRkN1dG9mZiA9IEZDdXRvZmYsIHRpc3N1ZSA9ICJzb2xldXMiKQpnZ3NhdmUoZmlsZW5hbWUgPSBmaWxlLnBhdGgocGF0aC5jdXJyZW50UGxvdHMsICIwMl92b2xjYW5vX3NvbGV1cy5zdmciKSwgcCkKcApgYGAKCiMjIHNjYXR0ZXItcGxvdDogbW9zdCBkaWZmZXJlbnRpYWwgZ2VuZXMsIGJvdGggdGlzc3VlcwoKdXNpbmcgdGhlIFdhbGQtdGVzdCBgc3RhdGAgZnJvbSB0aGUgREVTZXEyIHJlc3VsdCBhbmQgZmlsdGVyaW5nIG9uIHRoZSBzZXQKYEZDdXRvZmY9YGByIEZDdXRvZmZgIGFuZCBgcEN1dG9mZj1gYHIgcEN1dG9mZmAgeWllbGRzIHRoZSBmb2xsb3dpbmcgcGxvdDoKYGBge3Igc2NhdHRlckRFQSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodCA9IDksIGZpZy5yZXRpbmEgPSAyLCBkcGkgPSAxMDB9CmFwcGx5X2N1dG9mZnMgPC0gZnVuY3Rpb24oZGVzZXEucmVzdWx0LCBjb2xuYW1lPSJzdGF0IiwgRkN1dG9mZiwgcEN1dG9mZikgewogIHJlcy5maWx0ZXJlZCA8LSBkZXNlcS5yZXN1bHQgJT4lCiAgICBkYXRhLmZyYW1lKCkgJT4lCiAgICBmaWx0ZXIocGFkaiA8IHBDdXRvZmYsCiAgICAgICAgICAgbG9nMkZvbGRDaGFuZ2UgPiBGQ3V0b2ZmIHwgbG9nMkZvbGRDaGFuZ2UgPCAtRkN1dG9mZikgJT4lCiAgICBkcGx5cjo6cmVuYW1lKCEhY29sbmFtZSA6PSBzdGF0KSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoISFjb2xuYW1lKQogIHJldHVybihyZXMuZmlsdGVyZWQpCn0KCmdhc3Ryb2NfcmVzLmZpbHRlcmVkIDwtIGFwcGx5X2N1dG9mZnMocmVzLmdhc3Ryb2MsIGNvbG5hbWU9Imdhc3Ryb2MiLCBGQ3V0b2ZmLCBwQ3V0b2ZmKQpzb2xldXNfcmVzLmZpbHRlcmVkICA8LSBhcHBseV9jdXRvZmZzKHJlcy5zb2xldXMsICBjb2xuYW1lPSJzb2xldXMiLCAgRkN1dG9mZiwgcEN1dG9mZikKCmdlbmVfbmFtZXMgPC0gcm93RGF0YShzZS5nYXN0cm9jKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBkcGx5cjo6c2VsZWN0KGdlbmVfbmFtZSkKCiMgY29tYmluaW5nIFdhbGQtVGVzdCBkYXRhIGZyb20gYm90aCB0aXNzdWVzIGFuZCBvcmRlcmluZyBpbiBxdWFkcmFudHMKcmVzLmNvbWJpbmVkIDwtIG1lcmdlKGdhc3Ryb2NfcmVzLmZpbHRlcmVkLAogICAgICAgICAgICAgICAgICAgICAgc29sZXVzX3Jlcy5maWx0ZXJlZCwKICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gMCkgJT4lCiAgbXV0YXRlKGRpZmYuZXhwID0gY2FzZV93aGVuKAogICAgZ2FzdHJvYyA8IDAgJiBzb2xldXMgPCAwIH4gImJvdGggZG93biIsCiAgICBnYXN0cm9jID4gMCAmIHNvbGV1cyA+IDAgfiAiYm90aCB1cCIsCiAgICBnYXN0cm9jIDwgMCAmIHNvbGV1cyA+IDAgfiAiZ2FzdHJvY25lbWl1cyBkb3duLFxuIHNvbGV1cyB1cCIsCiAgICBnYXN0cm9jID4gMCAmIHNvbGV1cyA8IDAgfiAiZ2FzdHJvY25lbXVzIHVwLFxuIHNvbGV1cyBkb3duIiwKICAgIFRSVUUgfiAiZGlmZmVyZW50IgogICkpICU+JSAKICBtZXJnZShnZW5lX25hbWVzLCBieS54PSJSb3cubmFtZXMiLCBieS55PTApCiAKIyByZW1vdmluZyBhbGwgZ2VuZV9uYW1lcyBleGNlcHQgdGhlIHRvcF9uX2dlbmVzIChzdW0gb2YgYWJzb2x1dGUgV2FsZC10ZXN0IG51bWJlcnMpCnRvcF9uX2dlbmVzIDwtIDEwCnRvcF9sYWJlbHMgPC0gcmVzLmNvbWJpbmVkICU+JQogIGdyb3VwX2J5KGRpZmYuZXhwKSAlPiUgCiAgYXJyYW5nZShkZXNjKGFicyhnYXN0cm9jKSArIGFicyhzb2xldXMpKSkgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKSAlaW4lIGMoMTp0b3Bfbl9nZW5lcykpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIC4kZ2VuZV9uYW1lCgpyZXMuY29tYmluZWQgPC0gcmVzLmNvbWJpbmVkICU+JSAKICBtdXRhdGUoZ2VuZV9uYW1lID0gaWZlbHNlKGdlbmVfbmFtZSAlaW4lIHRvcF9sYWJlbHMsIGdlbmVfbmFtZSwgIiIpKQoKIyBmaW5hbCBwbG90CnAgPC0gZ2dwbG90KHJlcy5jb21iaW5lZCwgYWVzKHggPSBnYXN0cm9jLCB5ID0gc29sZXVzLCBsYWJlbCA9IGdlbmVfbmFtZSkpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApICsgCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBkaWZmLmV4cCkpICsKICAjIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJyZWQiLCAiY2hhcnRyZXVzZTEiLCAiYmlzcXVlIiwgInJveWFsYmx1ZSIpKSArCiAgbGFicyh4ID0gImdhc3Ryb2MiLCB5ID0gInNvbGV1cyIsIGNvbG9yID0gInNpZ25pZmljYW50bHlcbmRpZmZlcmVudGlhbGx5XG5leHByZXNzZWQiKSArCiAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChtYXgub3ZlcmxhcHMgPSAxNSkgKyAKICBnZ3RpdGxlKGxhYmVsID0gIkRFQTogdC1zdGF0aXN0aWNzIChXYWxkIHRlc3QpIikgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMpLAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMyksCiAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMpLAogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKQogICkKCmdnc2F2ZShmaWxlbmFtZSA9IGZpbGUucGF0aChwYXRoLmN1cnJlbnRQbG90cywgIjAyX2RlYV9zY2F0dGVyLnN2ZyIpLCBwKQpwCmBgYAoKCiMjIyBiYXJwbG90CgpgYGB7ciBkZWFfc2NhdHRlcl9iYXJwbG90fQpwIDwtIGdncGxvdChyZXMuY29tYmluZWQsIGFlcyh4ID0gZGlmZi5leHApKSArCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBkaWZmLmV4cCkpICsKICB0aGVtZV9idygpCmdnc2F2ZShmaWxlbmFtZSA9IGZpbGUucGF0aChwYXRoLmN1cnJlbnRQbG90cywgIjAyX2RlYV9zY2F0dGVyX2JhcnBsb3Quc3ZnIiksIHApCnAKYGBgCgpgYGB7ciBpbmNsdWRlPUZBTFNFfQojIG9idGFpbiBnZW5lIGNvdW50cyBmb3IgdGhlIHJlc3BlY3RpdmUgZ3JvdXBzIGZvciB2aXN1YWxpemluZwpzaWduX2dlbmVfc3RhdHMgPC0gbGlzdCgKICAiYWxsIGdlbmVzIiA9IG5yb3coZ2VuZV9uYW1lcyksCiAgImdhc3Ryb2MiID0gbnJvdyhnYXN0cm9jX3Jlcy5maWx0ZXJlZCksCiAgInNvbGV1cyIgPSBucm93KHNvbGV1c19yZXMuZmlsdGVyZWQpLAogICJzaGFyZWRfc2lnX2dlbmVzIiA9IG5yb3cocmVzLmNvbWJpbmVkKSwKICAiYm90aCB1cCIgPSBzdW0ocmVzLmNvbWJpbmVkJGRpZmYuZXhwID09ICJib3RoIHVwIiksCiAgImJvdGggZG93biIgPSBzdW0ocmVzLmNvbWJpbmVkJGRpZmYuZXhwID09ICJib3RoIGRvd24iKSwKICAiZ2EgdXAsIHNvbCBkb3duIiA9IHN1bShyZXMuY29tYmluZWQkZGlmZi5leHAgPT0gImdhIHVwLCBzb2wgZG93biIpLAogICJnYSBkb3duLCBzb2wgdXAiID0gc3VtKHJlcy5jb21iaW5lZCRkaWZmLmV4cCA9PSAiZ2EgZG93biwgc29sIHVwIikKKQpgYGAKCgojIyBWZW5uL0V1bGVyLURpYWdyYW0KCmBgYHtyIHZlbm5EaWFncmFtfQpjb2xfcGFsZXR0ZSA8LSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoOCwgIkRhcmsyIikKIyB2ZW5uLmNvbG9ycyA8LSBjKGdhc3Ryb2MgPSAiI0ZGQjA4RSIsIHNvbGV1cyA9ICIjRkY2NjM4IiwgY29sX3BhbGV0dGVbMTo0XSkKdmVubi5jb2xvcnMgPC0gYyh0aXNzdWVfcGFsLCBzY2FsZXM6Omh1ZV9wYWwoKSg0KSkKCiMgb25seSB0aGUgdHdvIHRpc3N1ZXMKZ2VuZV9zZXRzIDwtIGMoCiAgImdhc3Ryb2MiID0gc2lnbl9nZW5lX3N0YXRzJGdhc3Ryb2MgLSBzaWduX2dlbmVfc3RhdHMkc2hhcmVkX3NpZ19nZW5lcywKICAic29sZXVzIiA9IHNpZ25fZ2VuZV9zdGF0cyRzb2xldXMgLSBzaWduX2dlbmVfc3RhdHMkc2hhcmVkX3NpZ19nZW5lcywKICAiZ2FzdHJvYyZzb2xldXMiID0gc2lnbl9nZW5lX3N0YXRzJHNoYXJlZF9zaWdfZ2VuZXMKKQoKIyAxc3Qgb3B0aW9uOiBldWxlciB0d28gdGlzc3VlcwpwIDwtIHBsb3QoCiAgZXVsZXIoZ2VuZV9zZXRzKSwKICBxdWFudGl0aWVzID0gVCwKICBsZWdlbmQgPSBsaXN0KHNpZGUgPSAicmlnaHQiKSwKICBmaWxscyA9IHZlbm4uY29sb3JzLAogIG1haW4gPSAic2lnbmlmaWNhbnQgZ2VuZXMiCikKZ2dzYXZlKGZpbGVuYW1lID0gZmlsZS5wYXRoKHBhdGguY3VycmVudFBsb3RzLCAiMDJfZXVsZXIuc3ZnIiksIHApCnAKCiMgMm5kIG9wdGlvbjogdmVubiB0d28gdGlzc3VlcwpsaWJyYXJ5KGV1bGVycikKcCA8LSBwbG90KAogIGV1bGVycjo6dmVubihnZW5lX3NldHMpLAogIGZpbGxzID0gdGlzc3VlX3BhbCwKICBtYWluID0gInNpZ25pZmljYW50IGdlbmVzIgopCmdnc2F2ZShmaWxlbmFtZSA9IGZpbGUucGF0aChwYXRoLmN1cnJlbnRQbG90cywgIjAyX3Zlbm4uc3ZnIiksIHApCnAKYGBgCgoKIyMgSGVhdG1hcAoKYGBge3IgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9NCwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShDb21wbGV4SGVhdG1hcCkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCgp6c2NvcmUgPC0gZnVuY3Rpb24oTSkgewogIHMgPC0gYXBwbHkoIE0sMSxzZCApICAgICAgIyBzdGFuZGFyZCBkZXZpYXRpb24KICDCtSA8LSBhcHBseSggTSwgMSwgbWVhbiApICAjIG1lYW4KICBNLnogPC0gKE0gLSDCtSkgLyBzICAgICAgICAjIHpzY29yZQogIHJldHVybihNLnopCn0KCnNpZ25fZ2VuZXMgPC0KICB1bmlxdWUoYygKICAgIHJvdy5uYW1lcyhnYXN0cm9jX3Jlcy5maWx0ZXJlZCksCiAgICByb3cubmFtZXMoc29sZXVzX3Jlcy5maWx0ZXJlZCkKICApKSAKc2lnbl9nZW5lcyA8LQogIHNpZ25fZ2VuZXNbc2lnbl9nZW5lcyAlaW4lIHJvd25hbWVzKGNvdW50cy5nYXN0cm9jKSAmCiAgICAgICAgICAgICAgIHNpZ25fZ2VuZXMgJWluJSByb3duYW1lcyhjb3VudHMuc29sZXVzKV0KCmNvdW50c19zaWduX3pzY29yZWQgPC0gbWVyZ2UoCiAgY291bnRzLmdhc3Ryb2Nbc2lnbl9nZW5lcyxdLAogIGNvdW50cy5zb2xldXNbc2lnbl9nZW5lcyxdLAogIGJ5ID0gMCwKICBzdWZmaXhlcyA9IGMoIi5nYXN0cm9jIiwgIi5zb2xldXMiKQopICU+JSAKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcyh2YXI9IlJvdy5uYW1lcyIpICU+JQogIGFzLm1hdHJpeCgpICU+JSAKICB6c2NvcmUoKQoKCnBsb3RDSE0gPC0gZnVuY3Rpb24oY291bnRzX3NpZ24pIHsKICBteXBhbGV0dGUgPC0gYnJld2VyLnBhbCgxMSwgIlJkWWxCdSIpCiAgIyAocmV2ZXJzZSB0byBtYXAgc3R5bGUgb2Ygb3RoZXIgaGVhdG1hcCBpbiBtYW51c2NyaXB0KQogIG1vcmVjb2xzIDwtIGNvbG9yUmFtcFBhbGV0dGUocmV2KG15cGFsZXR0ZSkpCiAgCiAgdGlzc3VlX3ZlYyA8LSBjKHJlcCgiZ2FzdHJvYyIsIDEyKSwgcmVwKCJzb2xldXMiLCAxMikpCiAgY29uZGl0aW9uX3ZlYyA8LSBjKHJlcChjKHJlcCgiV1QiLCA2KSwgcmVwKCJLTyIsIDYpKSwyKSkKICB0b3BfYW5ub3QgPC0KICAgIEhlYXRtYXBBbm5vdGF0aW9uKAogICAgICB0aXNzdWUgPSB0aXNzdWVfdmVjLAogICAgICBjb25kaXRpb24gPSBjb25kaXRpb25fdmVjLAogICAgICBjb2wgPSBsaXN0KHRpc3N1ZSA9IHRpc3N1ZV9wYWwsIGNvbmRpdGlvbiA9IGMoIldUIiA9ICJ3aGl0ZSIsICJLTyIgPSAiZ3JheSIpKSwKICAgICAgZ3AgPSBncGFyKGNvbCA9ICJkYXJrZ3JheSIpLAogICAgICBzaG93X2xlZ2VuZCA9IEZBTFNFCiAgICApCiAgCiAgY2htIDwtIEhlYXRtYXAoCiAgICBjb3VudHNfc2lnbiwKICAgIHJvd190aXRsZSA9ICJkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMiLAogICAgbmFtZSA9ICJ6c2NvcmUiLAogICAgc2hvd19yb3dfbmFtZXMgPSBGQUxTRSwKICAgIHNob3dfY29sdW1uX25hbWVzID0gRkFMU0UsCiAgICBjb2x1bW5fdGl0bGUgPSAic2FtcGxlcyIsCiAgICBjb2wgPSByZXYobW9yZWNvbHMoNTApKSwKICAgIGNvbHVtbl90aXRsZV9zaWRlID0gImJvdHRvbSIsCiAgICB0b3BfYW5ub3RhdGlvbiA9IHRvcF9hbm5vdAogICkKICAKICAjIGNyZWF0aW5nIGN1c3RvbSBhbm5vdGF0aW9uIGxlZ2VuZCAodG8gb2J0YWluIHRoZSBncmF5IGJvcmRlcikKICBjb25kaXRpb25fbGVnZW5kID0gTGVnZW5kKAogICAgbGFiZWxzID0gYygiV1QiLCAiS08iKSwKICAgIGxlZ2VuZF9ncCA9IGdwYXIoZmlsbCA9IGMoIndoaXRlIiwgImdyYXkiKSksCiAgICBib3JkZXIgPSAiZGFya2dyYXkiLAogICAgdGl0bGUgPSAiY29uZGl0aW9uIgogICkKICB0aXNzdWVfbGVnZW5kID0gTGVnZW5kKAogICAgbGFiZWxzID0gYygiZ2FzdHJvYyIsICJzb2xldXMiKSwKICAgIGxlZ2VuZF9ncCA9IGdwYXIoZmlsbCA9IHVubmFtZSh0aXNzdWVfcGFsKSksCiAgICBib3JkZXIgPSAiZGFya2dyYXkiLAogICAgdGl0bGUgPSAidGlzc3VlIgogICkKICBsZWdlbmRfbGlzdCA8LSBsaXN0KGNvbmRpdGlvbl9sZWdlbmQsIHRpc3N1ZV9sZWdlbmQpCiAgCiAgZHJhdyhjaG0sIGFubm90YXRpb25fbGVnZW5kX2xpc3QgPSBsZWdlbmRfbGlzdCkKfQoKc3ZnbGl0ZTo6c3ZnbGl0ZSgKICBmaWxlLnBhdGgocGF0aC5jdXJyZW50UGxvdHMsICIwMl9oZWF0bWFwX3NpZ25fZ2VuZXMuc3ZnIiksCiAgd2lkdGggPSA2LAogIGhlaWdodCA9IDYKKQpwbG90Q0hNKGNvdW50c19zaWduX3pzY29yZWQpCmRldi5vZmYoKQpgYGAKCiFbXSguLi8uLi9wbG90cy9jdXJyZW50LzAzX2hlYXRtYXBfc2lnbl9nZW5lcy5zdmcpCgojIyMgcGNhOiBzaWduaWZpY2FudCBnZW5lcwpIZXJlIHRoZSB6LXNjb3JlZCBjb3VudCBtYXRyaXggaXMgdXNlZCB0byBjcmVhdGUgdGhlIHBjYSBwbG90LgpXZXRoZXIgdGhlIGNvdW50cyBhcmUgei1zY29yZWQgb3Igbm90LCBkb2VzIG5vdCBjaGFuZ2UgdGhlIHBsb3QsIGFzIGxvbmcgYXMgdGhlIHotc2NvcmUgaXMgYXBwbGllZCBvbiB0aGUgd2hvbGUgbWF0cml4IChub3QgcGVyIHRpc3N1ZSkKCmBgYHtyIHBjYS56c2NvcmVkLCBtZXNzYWdlPUZBTFNFfQpwY2EgPC0gcHJjb21wKHQoY291bnRzX3NpZ25fenNjb3JlZCksIHNjYWxlLiA9IFQpCgpwY2EuZGF0YSA8LSBkYXRhLmZyYW1lKFNhbXBsZSA9IHJvd25hbWVzKHBjYSR4KSwKICAgICAgICAgICAgICAgICAgICAgWCA9IHBjYSR4WywgMV0sCiAgICAgICAgICAgICAgICAgICAgIFkgPSBwY2EkeFssIDJdKSAlPiUKbXV0YXRlKGNvbmRpdGlvbiA9IHN1YnN0cihTYW1wbGUsIDEsIDIpLAogICAgICAgdGlzc3VlID0gc3RyaW5ncjo6c3RyX2V4dHJhY3QoU2FtcGxlLCJbOmFscGhhOl0rJCIpKQoKcCA8LQphdXRvcGxvdCgKICBwY2EsCiAgZGF0YSA9IHBjYS5kYXRhLAogIGNvbG91ciA9ICd0aXNzdWUnLAogIHNoYXBlID0gJ2NvbmRpdGlvbicsCiAgbGFiZWwuc2hvdy5sZWdlbmQgPSBGQUxTRQopICsKICBnZ3RpdGxlKCJQQ0Egc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMiKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHVubmFtZSh0aXNzdWVfcGFsKSkgKwogIHRoZW1lX2J3KCkKZ2dzYXZlKGZpbGVuYW1lID0gZmlsZS5wYXRoKHBhdGguY3VycmVudFBsb3RzLCAiMDJfcGNhX2RlYV9zaWduLnN2ZyIpLCBwKQpwCmBgYAoKIyAzLiBmZ3NlYQoKYGBge3IgZmdzZWFTZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShmZ3NlYSkKbGlicmFyeShwYXRjaHdvcmspICMgdG8gYWRkIGRvdHBsb3RzOiBwLmdhc3Ryb2MrICBwLnNvbGV1cwoKbG9hZCgiLi9kYXRhL1JvYmplY3RzLzAyX2Fubm90LlJEYXRhIikgIyBmb3IgZW50cmV6Z2VuZV9pZApgYGAKCgpgYGB7ciByZWFkaW5nX3BhdGh3YXlzLCBpbmNsdWRlPUZBTFNFfQojIGNhbm9uaWNhbCBwYXRod2F5cyAoMTY4NyBnZW5lIHNldHMpOgpDR1AgPC0gcXVzYWdlOjpyZWFkLmdtdCgiLi9kYXRhL3BhdGh3YXlzL20yLmNwLnYyMDIyLjEuTW0uZW50cmV6LmdtdCIpCmBgYAoKCiMjIHJhbmtzCgpgYGB7ciBnZXR0aW5nX3JhbmtzfQpnZXRSYW5rcyA8LSBmdW5jdGlvbihyZXMsIGFubm90KSB7CiAgIyBvbmx5IHRha2luZyBnZW5lcyB3aGljaCBoYXZlIGVudHJlemdlbmVfaWRzIGFzc2lnbmVkIHRvIHRoZW0KICBnZW5lc193aXRoX2VudHJleiA8LSBzZWxlY3QoYW5ub3QsIEdlbmVJRCwgZW50cmV6Z2VuZV9pZCkgJT4lIAogICAgZmlsdGVyKCFpcy5uYShlbnRyZXpnZW5lX2lkKSkKICAKICByYW5rcyA8LSBhcy5kYXRhLmZyYW1lKHJlcykgJT4lCiAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiR2VuZUlEIikgJT4lCiAgICBtZXJnZShnZW5lc193aXRoX2VudHJleiwgYnkgPSAiR2VuZUlEIikgJT4lCiAgICBhcnJhbmdlKGRlc2Moc3RhdCkpICU+JSAKICAgIHNlbGVjdChlbnRyZXpnZW5lX2lkLCBzdGF0KSAlPiUgCiAgICB0aWJibGU6OmRlZnJhbWUoKSAjIGNyZWF0aW5nIGEgbmFtZWQgbnVtIGZyb20gdHdvIGNvbHVtbnMKICByZXR1cm4ocmFua3MpCn0KCnJhbmtzLmdhc3Ryb2MgPC0gZ2V0UmFua3MocmVzLmdhc3Ryb2MsIGFubm90KQpyYW5rcy5zb2xldXMgPC0gZ2V0UmFua3MocmVzLnNvbGV1cywgYW5ub3QpCiMgVE9ETzogd2h5IChhZ2FpbikgaXMgdGhlIHNvbGV1cyBnZW5lIGNvdW50IHNlZW1pbmdseSAzMDAgYmVsb3cgc29sZXVzIGdlbmUgY291bnQgLyByYW5rcyBjb3VudAojIC0+IHNlZW1zIGJlY2F1c2Ugb2YgZS5nLiB0aGUgY3V0b2ZmIGF0IERFU2VxCmBgYAoKIyMgYXBwbHlpbmcgZmdzZWEKCiAgICBmZ3NlYVJlcyA8LSBmZ3NlYSgKICAgICAgcGF0aHdheXMgPSBDR1AsCiAgICAgIHN0YXRzICAgID0gcmFua3MsCiAgICAgIG1pblNpemUgID0gMTUsCiAgICAgIG1heFNpemUgID0gYHIgcGFyYW1zJGZnc2VhLm1heFNpemVgCiAgICApCgpgYGB7ciBmZ3NlYV9yZXNfbG9hZCwgaW5jbHVkZT1GQUxTRX0KIyAnIHJldHVybnMgdGhlIGZnc2VhIHJlc3VsdAojICcgYHRpc3N1ZWAgbmVlZHMgdG8gYmUgb25lIG9mIHsic29sZXVzIiwgInNvbGV1cyJ9CiMgJyBBbHNvIHNhdmVzIHRoZSByZXN1bHRzIGxvY2FsbHkgdG8gYXZvaWQgdG9vIG11Y2ggcXVlcmllcwpnZXRfZmdzZWFSZXMgPC0gZnVuY3Rpb24odGlzc3VlLCByYW5rcywgbWF4U2l6ZT1wYXJhbXMkZmdzZWEubWF4U2l6ZSwgcGF0aHdheXM9Q0dQLCBzYXZlUmVzdWx0cz1UKSB7CiAgcm9iamVjdHMucGF0aCA8LSBmaWxlLnBhdGgoIi4iLCAiZGF0YSIsICJSb2JqZWN0cyIpCiAgZmdzZWFSZXMucGF0aCA8LSBmaWxlLnBhdGgoCiAgICByb2JqZWN0cy5wYXRoLAogICAgIjA0X2Znc2VhX3Jlc3VsdHMiLAogICAgdGlzc3VlLAogICAgcGFzdGUwKCJtYXhTaXplXyIsIHBhcmFtcyRmZ3NlYS5tYXhTaXplLCAiLnJkcyIpCiAgKQogIGZnc2VhUmVzLmRpciA8LSBkaXJuYW1lKGZnc2VhUmVzLnBhdGgpCiAgCiAgaWYgKGZpbGUuZXhpc3RzKGZnc2VhUmVzLnBhdGgpICYgIXBhcmFtcyRyZWV2YWx1YXRlKSB7CiAgICBtZXNzYWdlKCJmZ3NlYSByZXN1bHQgZm91bmQgaW4gZGlyZWN0b3J5ISBQcmV2aW91cyB2ZXJzaW9uIHdpbGwgYmUgbG9hZGVkIC4uLiIpCiAgICByZXR1cm4ocmVhZFJEUyhmaWxlID0gZmdzZWFSZXMucGF0aCkpCiAgfSBlbHNlIHsKICAgICMgcGVyZm9ybSBmZ3NlYSBhbmQgc2F2ZSB0aGUgcmVzdWx0CiAgICBmZ3NlYVJlcyA8LSBmZ3NlYSgKICAgICAgcGF0aHdheXMgPSBwYXRod2F5cywKICAgICAgc3RhdHMgICAgPSByYW5rcywKICAgICAgbWluU2l6ZSAgPSAxNSwKICAgICAgbWF4U2l6ZSAgPSBtYXhTaXplCiAgICApCiAgICAKICAgIGlmIChzYXZlUmVzdWx0cykgewogICAgICBpZiAoIWRpci5leGlzdHMoZmdzZWFSZXMuZGlyKSkKICAgICAgICBkaXIuY3JlYXRlKGZnc2VhUmVzLmRpciwgcmVjdXJzaXZlID0gVCkKICAgICAgc2F2ZVJEUyhmZ3NlYVJlcywgZmlsZSA9IGZnc2VhUmVzLnBhdGgpCiAgICB9CiAgICByZXR1cm4oZmdzZWFSZXMpCiAgfQp9CgpmZ3NlYVJlcy5nYXN0cm9jIDwtIGdldF9mZ3NlYVJlcygiZ2FzdHJvYyIsIHJhbmtzLmdhc3Ryb2MsIHBhdGh3YXlzPUNHUCkKZmdzZWFSZXMuc29sZXVzIDwtIGdldF9mZ3NlYVJlcygic29sZXVzIiwgcmFua3Muc29sZXVzLCBwYXRod2F5cz1DR1ApCmBgYAoKCiMjIG1vc3QgZGlmZmVyZW50aWFsIHJlZ3VsYXRlZCBwYXRod2F5cywgYm90aCB0aXNzdWVzCgp1c2luZyBgTkVTYCBmcm9tIHRoZSBmZ3NlYSByZXN1bHQgZmlsdGVyaW5nIG9uIHRoZSBzZXQgYGBgIHBDdXRvZmY9YGByIHBhcmFtcyRwQ3V0b2ZmIGBgYCB5aWVsZHMgdGhlIGZvbGxvd2luZyBwbG90OgoKYGBge3IgdG9wRGlmZi5ib3RoLCB3YXJuaW5nPUZBTFNFfQoKcEN1dG9mZiA8LSBwYXJhbXMkcEN1dG9mZgpmZ3NlYVJlcy5jb21iaW5lZCA8LSBtZXJnZSgKICBkYXRhLmZyYW1lKGZnc2VhUmVzLmdhc3Ryb2NbLCBjKCJwYXRod2F5IiwgIk5FUyIsICJwYWRqIildKSwKICBkYXRhLmZyYW1lKGZnc2VhUmVzLnNvbGV1c1ssIGMoInBhdGh3YXkiLCAiTkVTIiwgInBhZGoiKV0pLAogIGJ5ID0gInBhdGh3YXkiLAogIHN1ZmZpeGVzID0gYygiLmdhIiwgIi5zb2wiKQopICU+JQogIGZpbHRlcihwYWRqLmdhIDwgcEN1dG9mZiB8IHBhZGouc29sIDwgcEN1dG9mZikgJT4lCiAgbXV0YXRlKAogICAgZGlmZi5leHAgPSBjYXNlX3doZW4oCiAgICAgIE5FUy5nYSAgPCAwICYgTkVTLnNvbCA8IDAgJiBwYWRqLmdhIDwgcEN1dG9mZiAmIHBhZGouc29sIDwgcEN1dG9mZiB+ICJib3RoIGRvd24iLAogICAgICBORVMuZ2EgID4gMCAmIE5FUy5zb2wgPiAwICYgcGFkai5nYSA8IHBDdXRvZmYgJiBwYWRqLnNvbCA8IHBDdXRvZmYgfiAiYm90aCB1cCIsCiAgICAgIE5FUy5nYSAgPCAwICYgTkVTLnNvbCA+IDAgJiBwYWRqLmdhIDwgcEN1dG9mZiAmIHBhZGouc29sIDwgcEN1dG9mZiB+ICJnYXN0cm9jbmVtaXVzIGRvd24sIHNvbGV1cyB1cCIsCiAgICAgIE5FUy5nYSAgPiAwICYgTkVTLnNvbCA8IDAgJiBwYWRqLmdhIDwgcEN1dG9mZiAmIHBhZGouc29sIDwgcEN1dG9mZiB+ICJnYXN0cm9jbmVtaXVzIHVwLCBzb2xldXMgZG93biIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWRqLmdhIDwgcEN1dG9mZiAmIHBhZGouc29sID4gcEN1dG9mZiB+ICJvbmx5IGdhc3Ryb2NuZW1pdXMiLAogICAgICAjIE5FUy5nYSAgPiAwICYgICAgICAgICAgICAgICBwYWRqLmdhIDwgcEN1dG9mZiAmIHBhZGouc29sID4gcEN1dG9mZiB+ICJnYSB1cCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWRqLmdhID4gcEN1dG9mZiAmIHBhZGouc29sIDwgcEN1dG9mZiB+ICJvbmx5IHNvbGV1cyIsCiAgICAgICMgTkVTLnNvbCA+IDAgJiAgICAgICAgICAgICAgIHBhZGouZ2EgPiBwQ3V0b2ZmICYgcGFkai5zb2wgPCBwQ3V0b2ZmIH4gInNvbCB1cCIsCiAgICAgIFRSVUUgfiAiZGlmZmVyZW50IgogICAgKQogICkKCiMgZmluYWwgcGxvdApwIDwtIGdncGxvdChmZ3NlYVJlcy5jb21iaW5lZCwgYWVzKHggPSBORVMuZ2EsIHkgPSBORVMuc29sLCB0ZXh0PXBhdGh3YXkpKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCkgKyAKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwKSArIAogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gZGlmZi5leHApKSArCiAgIyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygicmVkIiwgImNoYXJ0cmV1c2UxIiwgImJpc3F1ZSIsICJyb3lhbGJsdWUiKSkgKwogIGxhYnMoeCA9ICJnYXN0cm9jbmVtaXVzIiwgeSA9ICJzb2xldXMiLCBjb2xvciA9ICJzaWduaWZpY2FudGx5XG5kaWZmZXJlbnRpYWxseVxuZXhwcmVzc2VkIikgKwogICMgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChtYXgub3ZlcmxhcHMgPSAyMCkgKyAKICBnZ3RpdGxlKGxhYmVsID0gIk5vcm1hbGl6ZWQgRW5yaWNobWVudCBTY29yZSIpICsKICB0aGVtZV9idygpCgpnZ3NhdmUoZmlsZW5hbWUgPSBmaWxlLnBhdGgocGF0aC5jdXJyZW50UGxvdHMsICIwM19nc2VhX3NjYXR0ZXIuc3ZnIiksIHApCnAKCiMgaW50ZXJhY3RpdmU6CiMgcGxvdGx5OjpnZ3Bsb3RseShwLCB0b29sdGlwID0gImFsbCIpCmBgYAoKCiMjIyBiYXJwbG90CgpgYGB7ciBnc2VhX3NjYXR0ZXJfYmFycGxvdH0KcCA8LSBnZ3Bsb3QoZmdzZWFSZXMuY29tYmluZWQsIGFlcyh4ID0gZGlmZi5leHApKSArCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBkaWZmLmV4cCkpICsKICB0aGVtZV9idygpCgpnZ3NhdmUoZmlsZW5hbWUgPSBmaWxlLnBhdGgocGF0aC5jdXJyZW50UGxvdHMsICIwM19nc2VhX3NjYXR0ZXJfYmFycGxvdC5zdmciKSwgcCkKcApgYGAKCiMjIyBWZW5uL0V1bGVyLURpYWdyYW0KCmBgYHtyIHZlbm5EaWFncmFtR1NFQX0KY29sX3BhbGV0dGUgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDgsICJEYXJrMiIpCiMgdmVubi5jb2xvcnMgPC0gYyhnYXN0cm9jID0gIiNGRkIwOEUiLCBzb2xldXMgPSAiI0ZGNjYzOCIsIGNvbF9wYWxldHRlWzE6NF0pCnZlbm4uY29sb3JzIDwtIGModGlzc3VlX3BhbCwgc2NhbGVzOjpodWVfcGFsKCkoNCkpCgpzaWduX2dlbmVTZXRzX2dhc3Ryb2MgPC0KICBkYXRhLmZyYW1lKGZnc2VhUmVzLmdhc3Ryb2NbLCBjKCJwYXRod2F5IiwgInBhZGoiKV0pICU+JQogIGZpbHRlcihwYWRqIDwgcEN1dG9mZikgJT4lIAogIHB1bGwocGF0aHdheSkKCgpzaWduX2dlbmVTZXRzX3NvbGV1cyA8LQogIGRhdGEuZnJhbWUoZmdzZWFSZXMuc29sZXVzWywgYygicGF0aHdheSIsICJwYWRqIildKSAlPiUKICBmaWx0ZXIocGFkaiA8IHBDdXRvZmYpICU+JSAKICBwdWxsKHBhdGh3YXkpCgojIG9ubHkgdGhlIHR3byB0aXNzdWVzCmdlbmVfc2V0cyA8LSBsaXN0KAogICJnYXN0cm9jIiA9IHNpZ25fZ2VuZVNldHNfZ2FzdHJvYywKICAic29sZXVzIiA9IHNpZ25fZ2VuZVNldHNfc29sZXVzIywKICAjICJnYXN0cm9jJnNvbGV1cyIgPSBzaWduX2dlbmVfc3RhdHMkc2hhcmVkX3NpZ19nZW5lcwopCgojIDFzdCBvcHRpb246IGV1bGVyIHR3byB0aXNzdWVzCnAgPC0gcGxvdCgKICBldWxlcihnZW5lX3NldHMpLAogIHF1YW50aXRpZXMgPSBULAogIGxlZ2VuZCA9IGxpc3Qoc2lkZSA9ICJyaWdodCIpLAogIGZpbGxzID0gdmVubi5jb2xvcnMsCiAgbWFpbiA9ICJzaWduaWZpY2FudCBnZW5lIHNldHMgKEdTRUEpIgopCmdnc2F2ZShmaWxlbmFtZSA9IGZpbGUucGF0aChwYXRoLmN1cnJlbnRQbG90cywgIjAzX2V1bGVyLnN2ZyIpLCBwKQpwCgojIDJuZCBvcHRpb246IHZlbm4gdHdvIHRpc3N1ZXMKbGlicmFyeShldWxlcnIpCnAgPC0gcGxvdCgKICBldWxlcnI6OnZlbm4oZ2VuZV9zZXRzKSwKICBmaWxscyA9IHRpc3N1ZV9wYWwsCiAgbWFpbiA9ICJzaWduaWZpY2FudCBnZW5lIHNldHMgKEdTRUEpIgopCmdnc2F2ZShmaWxlbmFtZSA9IGZpbGUucGF0aChwYXRoLmN1cnJlbnRQbG90cywgIjAzX3Zlbm4uc3ZnIiksIHApCnAKYGBgCgoKIyMjIGRvdHBsb3QKYGBge3J9CmNvbWJpbmVkX2RvdHBsb3QgPC0KICBmdW5jdGlvbihncm91cCwKICAgICAgICAgICBmZ3NlYVJlcy5jb21iaW5lZCA9IGZnc2VhUmVzLmNvbWJpbmVkLAogICAgICAgICAgIG1heF9sYWJlbF9sZW5ndGggPSAyNSwgdG9wbj02ZTYsIHNvcnRfYnlfZ2FzdHJvYz1UKSB7CiAgICAKICAgICMgZ2V0IHBhdGh3YXlzIG9mIHRoZSByZXNwZWN0aXZlIGdyb3VwCiAgICBwYXRod2F5cyA8LSBmaWx0ZXIoZmdzZWFSZXMuY29tYmluZWQsIGRpZmYuZXhwID09IGdyb3VwKSRwYXRod2F5CiAgICAKICAgIGdhc3Ryb2MuZGYgPC0KICAgICAgZmlsdGVyKGZnc2VhUmVzLmdhc3Ryb2MsIHBhdGh3YXkgJWluJSBwYXRod2F5cykKICAgIHNvbGV1cy5kZiA8LQogICAgICBmaWx0ZXIoZmdzZWFSZXMuc29sZXVzLCBwYXRod2F5ICVpbiUgZ2FzdHJvYy5kZiRwYXRod2F5KQogICAgCiAgICBpZiAoc29ydF9ieV9nYXN0cm9jKSB7CiAgICAgIGdhc3Ryb2MuZGYgPC0gZ2FzdHJvYy5kZiAlPiUgCiAgICAgICAgdG9wX24odG9wbiwgd3QgPSBhYnMoTkVTKSkgJT4lCiAgICAgICAgYXJyYW5nZShORVMpCiAgICAgIHNvbGV1cy5kZiA8LSBzb2xldXMuZGYgJT4lIAogICAgICAgIC5bbWF0Y2goZ2FzdHJvYy5kZiRwYXRod2F5LCBwYXRod2F5KV0KICAgIH0gZWxzZSB7CiAgICAgIHNvbGV1cy5kZiA8LSBzb2xldXMuZGYgJT4lIAogICAgICAgIHRvcF9uKHRvcG4sIHd0ID0gYWJzKE5FUykpICU+JQogICAgICAgIGFycmFuZ2UoTkVTKQogICAgICBnYXN0cm9jLmRmIDwtIGdhc3Ryb2MuZGYgJT4lIAogICAgICAgIC5bbWF0Y2goc29sZXVzLmRmJHBhdGh3YXksIHBhdGh3YXkpXQogICAgfQogICAgCiAgICBwYWRqX2xpbWl0cyA8LQogICAgICBjKG1pbihnYXN0cm9jLmRmJHBhZGosIHNvbGV1cy5kZiRwYWRqKSwKICAgICAgICBtYXgoZ2FzdHJvYy5kZiRwYWRqLCBzb2xldXMuZGYkcGFkaikpCiAgICAKICAgIE5FU19saW1pdHMgPC0gCiAgICAgIGMobWluKGdhc3Ryb2MuZGYkTkVTLCBzb2xldXMuZGYkTkVTKSwKICAgICAgICBtYXgoZ2FzdHJvYy5kZiRORVMsIHNvbGV1cy5kZiRORVMpKQogICAgCiAgICBwLmdhc3Ryb2MgPC0gZmdzZWFfZG90cGxvdChnYXN0cm9jLmRmLCBtYXhfbGFiZWxfbGVuZ3RoLCBwYWRqX2xpbWl0cywgTkVTX2xpbWl0cykKICAgIAogICAgcC5zb2xldXMgPC0gZmdzZWFfZG90cGxvdChzb2xldXMuZGYsIG1heF9sYWJlbF9sZW5ndGgsIHBhZGpfbGltaXRzLCBORVNfbGltaXRzKQogICAgCiAgICAjIGFkanVzdCBwbG90cwogICAgcC5nYXN0cm9jIDwtIHAuZ2FzdHJvYyArCiAgICAgIHRoZW1lKAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAwLCAwLCAwKSwgIm5wYyIpLAogICAgICApICsKICAgICAgeWxhYigiTkVTXG4oZ2FzdHJvYykiKSArIAogICAgICB4bGFiKCIiKQogICAgcC5zb2xldXMgPC0gcC5zb2xldXMgKwogICAgICB0aGVtZSgKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAwLCAwLCAwKSwgIm5wYyIpCiAgICAgICkgKwogICAgICB5bGFiKCJORVNcbihzb2xldXMpIikgKyAKICAgICAgeGxhYigiIikKICAgIAogICAgcmV0dXJuKHAuZ2FzdHJvYyArIHAuc29sZXVzKQogIH0KCgpmZ3NlYV9kb3RwbG90IDwtCiAgZnVuY3Rpb24oZmdzZWFSZXMuZGYsCiAgICAgICAgICAgbWF4X2xhYmVsX2xlbmd0aD0yNSwKICAgICAgICAgICBwYWRqX2xpbWl0cz1OVUxMLAogICAgICAgICAgIE5FU19saW1pdHM9TlVMTCkgewogIHNpemVfYnJlYWtzIDwtIHNlcSgwLDIwMCxieT01MCkKICBzaXplX2JyZWFrc1sxXSA8LSAxMAogIHNpemVfcmFuZ2UgPC0gYygyLDgpCiAgCiAgIyBhZGp1c3QgbGFiZWwgc2l6ZSBpZiB0aGUgcGxvdCBoYXMgdG9vIG1hbnkgc2V0czoKICBsYWJlbF9zaXplIDwtIDgKICBuc2V0cyA8LSBucm93KGZnc2VhUmVzLmRmKQogIGlmIChuc2V0cyA+IDE1KSB7CiAgICAKICAgIGxhYmVsX3NpemUgPC0gbGFiZWxfc2l6ZSAtICgwLjA1ICogbnNldHMpCiAgICBtYXhfbGFiZWxfbGVuZ3RoIDwtIG5zZXRzICsgMTAKICAgIHNpemVfcmFuZ2UgPC0gc2l6ZV9yYW5nZSAtMQogIH0KICAKICBmZ3NlYVJlc19wcmVwLmRmIDwtIGZnc2VhUmVzLmRmICU+JQogICAgIyBUT0RPOiBtaWdodCBuZWVkIHRvIGJlIGNoYW5nZWQgaWYgb3RoZXIgbGFiZWxzIGFyZSB1c2VkIChsaWtlIGRlc2NyaXB0aW9uKQogICAgbXV0YXRlKHBhdGh3YXkgPSBnc3ViKCJfIiwgIiAiLCBwYXRod2F5KSAlPiUgdG9sb3dlcigpKSAlPiUKICAgIG11dGF0ZShwYXRod2F5ID0gZmFjdG9yKHBhdGh3YXksIGxldmVscyA9IHBhdGh3YXkpKQogICMgdGhlIGZhY3RvciBsZXZlbHMgbWlnaHQgYmUgZGlmZmVyZW50IHRoYW4gdGhlIG9yZGVyIG9mIHRoZSByb3dzCiAgCiAgZ2dwbG90KGZnc2VhUmVzX3ByZXAuZGYsIGFlcyh4ID0gcGF0aHdheSwgeSA9IE5FUykpICsKICAgIGdlb21fcG9pbnQoYWVzKHNpemUgPSBzaXplLCBjb2xvciA9IHBhZGopKSArCiAgICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAicmVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAiYmx1ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBwYWRqX2xpbWl0cykgKwogICAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gc2l6ZV9yYW5nZSwKICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzaXplX2JyZWFrcywKICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDEwLCAyMDApKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gTkVTX2xpbWl0cykgKwogICAgIyBzY2FsZV94X2Rpc2NyZXRlKGd1aWRlID0gZ3VpZGVfYXhpcyhjaGVjay5vdmVybGFwID0gVFJVRSkpKwogICAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX3dyYXAobWF4X2xhYmVsX2xlbmd0aCkpICsKICAgIHhsYWIoIkdlbmUgU2V0IikgKyB5bGFiKCJORVMiKSArCiAgICBsYWJzKHNpemUgPSAiQ291bnQiLCBjb2xvciA9ICJwLmFkanVzdCIpICsKICAgIHRoZW1lX2J3KCkgKwogICAgY29vcmRfZmxpcCgpICsKICAgIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSBsYWJlbF9zaXplKSkKICB9CmBgYAoKIU5vdGU6IERpZmZlcmVudCB0byBtb3N0IG90aGVyIHBsb3RzLCB0aGUgZG90cGxvdHMgc2VlbSB0byBiZSBjcmVhdGVkIGJlc3QgYnkKcnVubmluZyB0aGUgY2h1bmtzIG1hbnVhbGx5LiBUaGlzIHJlc3VsdHMgaW4gYXV0b21hdGljIGF4aXMgc2NhbGluZyB3aXRoIG1vcmUgCmVudHJpZXMsIHNvIHRoYXQgdGhlIGxhYmVscyBkbyBub3Qgb3ZlcmxhcCAod2hpY2ggaXMgY3VyaW91c2x5IG5vdCB0aGUgY2FzZSBpZgpyZW1vdGVseSBrbml0dGluZykhCgoKIyMjIyBkb3RwbG90OiAiYm90aCB1cHJlZ3VsYXRlZCIKb25seSA3IGdlbmUgc2V0cyA9PiBmaXRzIHdlbGwgaW4gdGhlIHBsb3QKYGBge3IgZG90cGxvdEJvdGhVcH0KZ3JvdXAgPSAiYm90aCB1cCIKcF9jb21iaW5lZCA8LSBjb21iaW5lZF9kb3RwbG90KGdyb3VwLCBmZ3NlYVJlcy5jb21iaW5lZCkKCiMgZ2dzYXZlKGZpbGVuYW1lID0gZmlsZS5wYXRoKHBhdGguY3VycmVudFBsb3RzLCBwYXN0ZTAoIjAzX2dzZWFfZG90cGxvdF8iLCBncm91cCwgIi5zdmciKSksCiMgICAgICAgIHBfY29tYmluZWQpCnBfY29tYmluZWQKYGBgCgoKIyMjIyBkb3RwbG90OiAiYm90aCBkb3duIgpnZXRzIGFkanVzdGVkIGJ5IHRoZSBmdW5jdGlvbiB3ZWxsLiBUaGUgLnN2ZyBmaWxlIGxvb2tzIG9rYXkKYGBge3IgZG90cGxvdEJvdGhEb3dufQpncm91cCA9ICJib3RoIGRvd24iCnBfY29tYmluZWQgPC0gY29tYmluZWRfZG90cGxvdChncm91cCwgZmdzZWFSZXMuY29tYmluZWQpCgojIGdnc2F2ZShmaWxlbmFtZSA9IGZpbGUucGF0aChwYXRoLmN1cnJlbnRQbG90cywgcGFzdGUwKCIwM19nc2VhX2RvdHBsb3RfIiwgZ3JvdXAsICIuc3ZnIikpLAojICAgICAgICBwX2NvbWJpbmVkKQpwX2NvbWJpbmVkCmBgYAoKCiMjIyMgZG90cGxvdDogIm9ubHkgZ2FzdHJvY25lbWl1cyIKZ2V0cyBhZGp1c3RlZCBieSB0aGUgZnVuY3Rpb24gd2VsbC4gVGhlIC5zdmcgZmlsZSBsb29rcyBva2F5CgpgYGB7ciBkb3RwbG90R2FzdHJvY30KZ3JvdXAgPSAib25seSBnYXN0cm9jbmVtaXVzIgpwX2NvbWJpbmVkIDwtIGNvbWJpbmVkX2RvdHBsb3QoZ3JvdXAsIGZnc2VhUmVzLmNvbWJpbmVkLCBtYXhfbGFiZWxfbGVuZ3RoID0gMTAwKQoKIyBnZ3NhdmUoZmlsZW5hbWUgPSBmaWxlLnBhdGgocGF0aC5jdXJyZW50UGxvdHMsIHBhc3RlMCgiMDNfZ3NlYV9kb3RwbG90XyIsIGdyb3VwLCAiLnN2ZyIpKSwKIyAgICAgICAgcF9jb21iaW5lZCkKcF9jb21iaW5lZApgYGAKCmBgYHtyIGRvdHBsb3RHYXN0cm9jU2luZ2xlfQpncm91cCA9ICJvbmx5IGdhc3Ryb2NuZW1pdXMiCnBhdGh3YXlzIDwtIGZpbHRlcihmZ3NlYVJlcy5jb21iaW5lZCwgZGlmZi5leHAgPT0gZ3JvdXApJHBhdGh3YXkKCmdhc3Ryb2MuZGYgPC0KICBmaWx0ZXIoZmdzZWFSZXMuZ2FzdHJvYywgcGF0aHdheSAlaW4lIHBhdGh3YXlzKSAlPiUKICBhcnJhbmdlKE5FUykKCnAuZ2FzdHJvYyA8LQogIGZnc2VhX2RvdHBsb3QoZ2FzdHJvYy5kZiwgbWF4X2xhYmVsX2xlbmd0aCA9IDEwMCkKIyBnZ3NhdmUoZmlsZW5hbWUgPSBmaWxlLnBhdGgocGF0aC5jdXJyZW50UGxvdHMsIHBhc3RlMCgiMDNfZ3NlYV9kb3RwbG90XyIsIGdyb3VwLCAiX3NpbmdsZS5zdmciKSksCiMgICAgICAgIHAuZ2FzdHJvYykKcC5nYXN0cm9jCmBgYAoKCgojIyMjIGRvdHBsb3Q6ICJvbmx5IHNvbGV1cyIKZ2V0cyBhZGp1c3RlZCBieSB0aGUgZnVuY3Rpb24gd2VsbC4gVGhlIC5zdmcgZmlsZSBsb29rcyBva2F5CgpgYGB7ciBkb3RwbG90U29sZXVzfQpncm91cCA9ICJvbmx5IHNvbGV1cyIKcF9jb21iaW5lZCA8LSBjb21iaW5lZF9kb3RwbG90KGdyb3VwLCBmZ3NlYVJlcy5jb21iaW5lZCwgbWF4X2xhYmVsX2xlbmd0aCA9IDEwMCwgc29ydF9ieV9nYXN0cm9jID0gRikKCiMgZ2dzYXZlKGZpbGVuYW1lID0gZmlsZS5wYXRoKHBhdGguY3VycmVudFBsb3RzLCBwYXN0ZTAoIjAzX2dzZWFfZG90cGxvdF8iLCBncm91cCwgIi5zdmciKSksCiMgICAgICAgIHBfY29tYmluZWQpCnBfY29tYmluZWQKYGBgCgoKYGBge3IgZG90cGxvdFNvbGV1c1NpbmdsZX0KZ3JvdXAgPSAib25seSBzb2xldXMiCnBhdGh3YXlzIDwtIGZpbHRlcihmZ3NlYVJlcy5jb21iaW5lZCwgZGlmZi5leHAgPT0gZ3JvdXApJHBhdGh3YXkKCnNvbGV1cy5kZiA8LQogIGZpbHRlcihmZ3NlYVJlcy5zb2xldXMsIHBhdGh3YXkgJWluJSBwYXRod2F5cykgJT4lCiAgYXJyYW5nZShORVMpCgpwLnNvbGV1cyA8LQogIGZnc2VhX2RvdHBsb3Qoc29sZXVzLmRmLCBtYXhfbGFiZWxfbGVuZ3RoID0gMTAwKQojIGdnc2F2ZShmaWxlbmFtZSA9IGZpbGUucGF0aChwYXRoLmN1cnJlbnRQbG90cywgcGFzdGUwKCIwM19nc2VhX2RvdHBsb3RfIiwgZ3JvdXAsICJfc2luZ2xlLnN2ZyIpKSwKIyAgICAgICAgcC5zb2xldXMpCnAuc29sZXVzCmBgYAoKIyMjIyBkb3RwbG90OiB0b3AyMApgYGB7ciBpbmNsdWRlPUZBTFNFfQpmb3IgKGdyb3VwIGluIHVuaXF1ZShmZ3NlYVJlcy5jb21iaW5lZCRkaWZmLmV4cCkpIHsKICBwX2NvbWJpbmVkIDwtIGNvbWJpbmVkX2RvdHBsb3QoZ3JvdXAsIGZnc2VhUmVzLmNvbWJpbmVkKQogIAogIGdnc2F2ZShmaWxlbmFtZSA9IGZpbGUucGF0aChwYXRoLmN1cnJlbnRQbG90cywgcGFzdGUwKCIwM19nc2VhX2RvdHBsb3RfIiwgZ3JvdXAsICIuc3ZnIikpLAogICAgICAgICBwX2NvbWJpbmVkKQogIHByaW50KHBfY29tYmluZWQpCn0KYGBgCgo=